/**
 * 
 */
package org.shanktech.tshare.binlogsync.listener;

import java.util.List;

import org.shanktech.tshare.binlogsync.facotry.BinlogDataFactory;
import org.shanktech.tshare.binlogsync.model.binlog.BinlogData;
import org.shanktech.tshare.binlogsync.queue.BinlogQueue;
import org.shanktech.tshare.binlogsync.util.Constant.BinlogFormat;
import org.springframework.util.StringUtils;

import com.google.code.or.binlog.BinlogEventListener;
import com.google.code.or.binlog.BinlogEventV4;
import com.google.code.or.binlog.impl.event.DeleteRowsEvent;
import com.google.code.or.binlog.impl.event.QueryEvent;
import com.google.code.or.binlog.impl.event.TableMapEvent;
import com.google.code.or.binlog.impl.event.UpdateRowsEvent;
import com.google.code.or.binlog.impl.event.WriteRowsEvent;

public class NotificationListener implements BinlogEventListener {

  /**
   * mysql日志模式
   */
  private BinlogFormat mode;

  /**
   * 要处理的数据库
   */
  private List<String> dataBases;

  /**
   * 要处理的表
   */
  private List<String> tables;

  /**
   * 当前事件正在操作的数据库
   */
  private String currentDataBase = "";

  /**
   * 当前事件正在操作的表
   */
  private String currentTable = "";

  public NotificationListener(BinlogFormat mode) {
    super();
    this.mode = mode;
  }

  public NotificationListener(BinlogFormat mode, List<String> dataBases,
      List<String> tables) {
    super();
    this.mode = mode;
    this.dataBases = dataBases;
    this.tables = tables;
  }

  @Override
  public void onEvents(BinlogEventV4 event) {
    if (event == null) {
      return;
    }

    try {
      // 在mixed模式下只關心QueryEvent事件
      if (mode.equals(BinlogFormat.MIXED) && event instanceof QueryEvent) {
        QueryEvent queryEvent = (QueryEvent) event;
        String databaseName = queryEvent.getDatabaseName().toString();
        databaseName = StringUtils.isEmpty(databaseName)
            ? getQueryEventDatabaseName(queryEvent.getSql().toString())
            : databaseName;
        String tableName = getQueryEventTableName(databaseName,
            queryEvent.getSql().toString());
        if (!StringUtils.isEmpty(tableName)
            && needConvert(databaseName, tableName)) {
          BinlogData elment =
              BinlogDataFactory.getInstance(event, databaseName, tableName);
          if (null != elment) {
            BinlogQueue.put(elment);
          }
        }

      } else if (event instanceof TableMapEvent) {
        // 如果是row模式，需要从TableMapEvent事件中先获取到操作的数据库以及操作的表名
        TableMapEvent tableMapEvent = (TableMapEvent) event;
        this.currentDataBase = tableMapEvent.getDatabaseName().toString();
        this.currentTable = tableMapEvent.getTableName().toString();

      } else if ((event instanceof UpdateRowsEvent // 如果是增删改事件则进行处理
          || event instanceof DeleteRowsEvent
          || event instanceof WriteRowsEvent)
          && needConvert(this.currentDataBase, this.currentTable)) {
        processRowFormateModel(event);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  /**
   * 按照行模式進行解析事件
   * 
   * @param event
   */
  private void processRowFormateModel(BinlogEventV4 event) {
    try {
      BinlogData elment =
          BinlogDataFactory.getInstance(event, this.currentDataBase, this.currentTable);
      if (null != elment) {
        BinlogQueue.put(elment);
      }
    } catch (InterruptedException e) {
    } finally {
      resetCurrentData();
    }
  }

  /**
   * 判斷是否需要轉換
   * 
   * @param databaseName
   * @param tableName
   * @return
   */
  private boolean needConvert(String databaseName, String tableName) {
    return dataBases.contains(
        StringUtils.isEmpty(databaseName) ? "" : databaseName.toLowerCase())
        && tables.contains(
            StringUtils.isEmpty(tableName) ? "" : tableName.toLowerCase());
  }

  /**
   * 解析sql語句從sql匹配到數據名稱
   * 
   * @param sql
   * @return
   */
  private String getQueryEventDatabaseName(String sql) {
    for (String item : this.dataBases) {
      if (isDatabaseName(sql.trim(), item.trim())) {
        return item;
      }
    }
    return "";
  }

  private boolean isDatabaseName(String sql, String name) {
    sql = sql.replaceAll("\n", "");
    sql = sql.replaceAll("\r", "");

    return 0 <= sql.indexOf("insert into `" + name + "`.") // eg: insert into
                                                           // `dbname`.
        || 0 <= sql.indexOf("delete from `" + name + "`.")
        || 0 <= sql.indexOf("update `" + name + "`.")

        || 0 <= sql.indexOf("insert into " + name + ".") // eg: insert into
                                                         // dbname.
        || 0 <= sql.indexOf("delete from " + name + ".")
        || 0 <= sql.indexOf("update " + name + ".")

        || 0 <= sql.indexOf("INSERT INTO `" + name + "`.") // eg: INSERT INTO
                                                           // `dbname`.
        || 0 <= sql.indexOf("DELETE FROM `" + name + "`.")
        || 0 <= sql.indexOf("UPDATE `" + name + "`.")

        || 0 <= sql.indexOf("INSERT INTO " + name + ".") // eg: INSERT INTO
                                                         // dbname.
        || 0 <= sql.indexOf("DELETE FROM " + name + ".")
        || 0 <= sql.indexOf("UPDATE " + name + ".");
  }

  private boolean isTableName(String databaseName, String sql, String name) {
    sql = sql.replaceAll("\n", "");
    sql = sql.replaceAll("\r", "");

    return 0 <= sql.indexOf("`" + databaseName + "`." + "`" + name + "`") // eg:`dbname`.`tablename`

        || 0 <= sql.indexOf(databaseName + "." + name) // eg: dbname.tablename

        || 0 <= sql.indexOf("INSERT INTO `" + name + "`") // eg: INSERT INTO
                                                          // `tablename`
        || 0 <= sql.indexOf("DELETE FROM `" + name + "`")
        || 0 <= sql.indexOf("UPDATE `" + name + "`")

        || 0 <= sql.indexOf("INSERT INTO " + name + ".") // eg: INSERT INTO
                                                         // tablename
        || 0 <= sql.indexOf("DELETE FROM " + name + ".")
        || 0 <= sql.indexOf("UPDATE " + name + ".");
  }

  /**
   * 根據匹配到的數據庫名稱從sql語句中獲取表名稱
   * 
   * @param databaseName
   * @param sql
   * @return
   */
  private String getQueryEventTableName(String databaseName, String sql) {
    if (!StringUtils.isEmpty(databaseName) && !StringUtils.isEmpty(sql)) {
      for (String item : this.tables) {
        if (isTableName(databaseName, sql.trim(), item.trim())) {
          return item;
        }
      }
    }
    return "";
  }

  private void resetCurrentData() {
    this.currentDataBase = "";
    this.currentTable = "";
  }

  public BinlogFormat getMode() {
    return mode;
  }

  public void setMode(BinlogFormat mode) {
    this.mode = mode;
  }

  public List<String> getDataBases() {
    return dataBases;
  }

  public void setDataBases(List<String> dataBases) {
    this.dataBases = dataBases;
  }

  public List<String> getTables() {
    return tables;
  }

  public void setTables(List<String> tables) {
    this.tables = tables;
  }

  public String getCurrentDataBase() {
    return currentDataBase;
  }

  public void setCurrentDataBase(String currentDataBase) {
    this.currentDataBase = currentDataBase;
  }

  public String getCurrentTable() {
    return currentTable;
  }

  public void setCurrentTable(String currentTable) {
    this.currentTable = currentTable;
  }

}
